﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using EasyHook;

namespace FileMonInject
{
    public enum FontCharSet : byte
    {
        ANSI_CHARSET = 0,
        DEFAULT_CHARSET = 1,
        SYMBOL_CHARSET = 2,
        MAC_CHARSET = 77,
        SHIFTJIS_CHARSET = 128,
        HANGEUL_CHARSET = 129,
        HANGUL_CHARSET = 129,
        GB2312_CHARSET = 134,
        CHINESEBIG5_CHARSET = 136,
        JOHAB_CHARSET = 130,
        HEBREW_CHARSET = 177,
        ARABIC_CHARSET = 178,
        GREEK_CHARSET = 161,
        TURKISH_CHARSET = 162,
        VIETNAMESE_CHARSET = 163,
        BALTIC_CHARSET = 186,
        RUSSIAN_CHARSET = 204,
        THAI_CHARSET = 222,
        EASTEUROPE_CHARSET = 238,
        OEM_CHARSET = 255
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class LOGFONT
    {
        public int lfHeight = 0;
        public int lfWidth = 0;
        public int lfEscapement = 0;
        public int lfOrientation = 0;
        public int lfWeight = 0;
        public byte lfItalic = 0;
        public byte lfUnderline = 0;
        public byte lfStrikeOut = 0;
        public byte lfCharSet = 0;
        public byte lfOutPrecision = 0;
        public byte lfClipPrecision = 0;
        public byte lfQuality = 0;
        public byte lfPitchAndFamily = 0;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string lfFaceName = string.Empty;
    }

    [Flags]
    public enum ETOOptions : uint
    {
        ETO_CLIPPED = 0x4,
        ETO_GLYPH_INDEX = 0x10,
        ETO_IGNORELANGUAGE = 0x1000,
        ETO_NUMERICSLATIN = 0x800,
        ETO_NUMERICSLOCAL = 0x400,
        ETO_OPAQUE = 0x2,
        ETO_PDY = 0x2000,
        ETO_RTLREADING = 0x800,
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct DRAWTEXTPARAMS
    {
        public uint cbSize;
        public int iTabLength;
        public int iLeftMargin;
        public int iRightMargin;
        public uint uiLengthDrawn;
    }

    public class Main : EasyHook.IEntryPoint
    {
        FileMon.FileMonInterface Interface;
        LocalHook CreateFileHook;
        Stack<String> Queue = new Stack<String>();

        public Main(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            // connect to host...
            Interface = RemoteHooking.IpcConnectClient<FileMon.FileMonInterface>(InChannelName);

            Interface.Ping();
        }

        public void Run(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            // install hook...
            try
            {
                CreateFileHook = LocalHook.Create(
                    LocalHook.GetProcAddress("gdi32.dll", "ExtTextOutW"),
                    new DCExtTextOutW(ExtTextOutW_Hooked),
                    this);

                //CreateFileHook = LocalHook.Create(
                //    LocalHook.GetProcAddress("kernel32.dll", "CreateFileW"),
                //    new DCreateFile(CreateFile_Hooked),
                //    this);

                CreateFileHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
            }
            catch (Exception ExtInfo)
            {
                Interface.ReportException(ExtInfo);

                return;
            }

            Interface.IsInstalled(RemoteHooking.GetCurrentProcessId());

            RemoteHooking.WakeUpProcess();

            // wait for host process termination...
            try
            {
                while (true)
                {
                    Thread.Sleep(500);

                    // transmit newly monitored file accesses...
                    if (Queue.Count > 0)
                    {
                        String[] Package = null;

                        lock (Queue)
                        {
                            Package = Queue.ToArray();

                            Queue.Clear();
                        }

                        Interface.OnCreateFile(RemoteHooking.GetCurrentProcessId(), Package);
                    }
                    else
                        Interface.Ping();
                }
            }
            catch
            {
                // Ping() will raise an exception if host is unreachable
            }
        }

        delegate IntPtr DCExtTextOutW(IntPtr hdc, int X, int Y, uint fuOptions, IntPtr lprc, [MarshalAs(UnmanagedType.LPWStr)] String lpString, int cbCount, IntPtr lpDx);

        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        static IntPtr ExtTextOutW_Hooked(IntPtr hDC, int X, int Y, uint fuOptions, IntPtr lprc, [MarshalAs(UnmanagedType.LPWStr)]  String lpString, int cbCount, IntPtr lpDx)
        {
            bool bRet = false;
            int nRetCount = cbCount;
            StringBuilder pszAscii = new StringBuilder(cbCount);
            StringBuilder pszWstr = new StringBuilder((int)cbCount);
            LOGFONT lf = new LOGFONT();
            Font hFont = Font.FromHdc(hDC);
            hFont.ToLogFont(lf);

            try
            {
                if (fuOptions == (uint)ETOOptions.ETO_GLYPH_INDEX)
                {
                    pszAscii = ConvertGlyphIndiciesToASCII(hDC, lpString, cbCount, out nRetCount);
                    if (nRetCount > 0)
                    {
                        bRet = true;
                    }
                }
            }
            catch (SystemException e)
            {
                MessageBox.Show("!GLYPH_EX: " + e.Message);
            }
            try
            {
                Main This = (Main)HookRuntimeInfo.Callback;
                lock (This.Queue)
                {
                    if (bRet == false && lpString != String.Empty && lpString != null)//&& lpString != String.Empty && lpString != " "
                    {
                        try
                        {
                            //MultiByteToWideChar((int)(FontCharSet)hFont.GdiCharSet, 0, GetBytes(lpString)/*Encoding.ASCII.GetString(pszAscii)*/, cbCount, pszWstr, 1024);
                            //var m = Utf8ToUnicode(lpString);
                            //This.Queue.Push(m);
                            StringBuilder sb = new StringBuilder(cbCount);
                            sb.Append(lpString);
                            This.Queue.Push(sb.ToString() + " " + fuOptions.ToString("X"));
                            //This.Queue.Push(Utf8ToUnicode(lpString) + " " + fuOptions.ToString("X"));

                        }
                        catch (EncoderFallbackException e)
                        {
                            MessageBox.Show("!CEEX: " + e.Message);
                        }

                    }
                    else if (bRet == true && pszAscii.ToString() != String.Empty && pszAscii.ToString() != null)
                    {
                        This.Queue.Push("[ETO(GLYPH) : " + " \"" + pszAscii.ToString() + "\"");
                    }

                }
            }
            catch (System.ArgumentException e)
            {
                MessageBox.Show("!CALLBACK_EX: " + e.Message);
            }

            return ExtTextOutW(hDC, X, Y, fuOptions, lprc, lpString, cbCount, lpDx);
        }

        private static string Utf8ToUnicode(string input)
        {
            try
            {
                return Encoding.UTF8.GetString(input.Select(item => (byte)item).ToArray());
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }

        public static StringBuilder ConvertGlyphIndiciesToASCII(IntPtr hDC, string lpString, int nCount, out int nRetCount)
        {
            const int nChars = 191;
            char[] pszChars = new char[nChars];
            StringBuilder pszRet = new StringBuilder();
            if (0 == pszChars[0])
            {

                for (int n = 0; n < nChars - 32; n++)
                {
                    pszChars[n] = Convert.ToChar(Convert.ToChar(n) + Convert.ToChar(32));//

                }

            }
            ushort[] pgi = new ushort[nChars];
            GetGlyphIndices(hDC, new String(pszChars), nChars, pgi, 0);
            List<CharRange> charMap = new List<CharRange>();
            for (int i = 0; i < nChars; i++)
            {
                CharRange range = new CharRange();
                range.Pos = pgi[i];
                range.Char = pszChars[i];
                charMap.Add(range);
            }
            nRetCount = 0;
            for (int n = 0; n < lpString.Length; n++)
            {
                for (int c = 0; c < nChars; c++)
                {
                    CharRange res = charMap[c];
                    if (res.Pos == lpString[n])
                    {
                        pszRet.Append(res.Char);
                        nRetCount++;
                    }
                }
            }
            return pszRet;
        }

        [DllImport("gdi32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr ExtTextOutW(IntPtr hdc, int X, int Y, uint fuOptions, IntPtr lprc, [MarshalAs(UnmanagedType.LPWStr)] String lpString, int cbCount, IntPtr lpDx);

        [DllImport("gdi32.dll")]
        static extern uint GetGlyphIndices(IntPtr hdc, string lpsz, int c, [Out] ushort[] pgi, uint fl);

        #region ExtTextOut

        //[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
        //private delegate bool DExtTextOut(IntPtr hdc,
        //                                  int X,
        //                                  int Y,
        //                                  uint fuOptions,
        //                                  [In] ref RECT lprc,
        //                                  [MarshalAs(UnmanagedType.LPWStr)] String lpString,
        //                                  uint cbCount,
        //                                  [In] IntPtr lpDx);

        //[DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        //static extern bool ExtTextOutW(IntPtr hdc,
        //                               int X,
        //                               int Y,
        //                               uint fuOptions,
        //                               [In] ref RECT lprc,
        //                               [MarshalAs(UnmanagedType.LPWStr)] String lpString,
        //                               uint cbCount,
        //                               [In] IntPtr lpDx);

        //static bool ExtTextOut_Hooked(IntPtr hdc,
        //                               int X,
        //                               int Y,
        //                               uint fuOptions,
        //                               [In] ref RECT lprc,
        //                               [MarshalAs(UnmanagedType.LPWStr)] String lpString,
        //                               uint cbCount,
        //                               [In] IntPtr lpDx)
        //{
        //    try
        //    {
        //        Main This = (Main)HookRuntimeInfo.Callback;

        //        lock (This.Queue)
        //        {
        //            This.Queue.Push("[" + RemoteHooking.GetCurrentProcessId() + ":" +
        //                RemoteHooking.GetCurrentThreadId() + "]: \"" + lpString + "\"");
        //        }
        //    }
        //    catch
        //    {
        //    }

        //    return ExtTextOutW(hdc, X, Y, fuOptions, ref lprc, lpString, cbCount, lpDx);
        //}

        #endregion

        #region CreateFile

        [UnmanagedFunctionPointer(CallingConvention.StdCall,
            CharSet = CharSet.Unicode,
            SetLastError = true)]
        delegate IntPtr DCreateFile(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);

        // just use a P-Invoke implementation to get native API access from C# (this step is not necessary for C++.NET)
        [DllImport("kernel32.dll",
            CharSet = CharSet.Unicode,
            SetLastError = true,
            CallingConvention = CallingConvention.StdCall)]
        static extern IntPtr CreateFile(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);

        // this is where we are intercepting all file accesses!
        static IntPtr CreateFile_Hooked(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile)
        {

            try
            {
                Main This = (Main)HookRuntimeInfo.Callback;

                lock (This.Queue)
                {
                    This.Queue.Push("[" + RemoteHooking.GetCurrentProcessId() + ":" +
                        RemoteHooking.GetCurrentThreadId() + "]: \"" + InFileName + "\"");
                }
            }
            catch
            {
            }

            // call original API...
            return CreateFile(
                InFileName,
                InDesiredAccess,
                InShareMode,
                InSecurityAttributes,
                InCreationDisposition,
                InFlagsAndAttributes,
                InTemplateFile);
        }

        #endregion

        #region DrawText

        private delegate int DrawTextExDelegate(IntPtr hdc, string lpchText, int cchText,
                ref RECT lprc, uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams);

        private int DrawTextEx_Hooked(IntPtr hdc, string lpchText, int cchText, ref RECT lprc,
                                             uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams)
        {
            try
            {
                Main This = (Main)HookRuntimeInfo.Callback;

                lock (This.Queue)
                {
                    This.Queue.Push("[" + RemoteHooking.GetCurrentProcessId() + ":" +
                        RemoteHooking.GetCurrentThreadId() + "]: \"" + lpchText + "\"");
                }
            }
            catch
            {
            }
            return DrawTextEx(hdc, lpchText, cchText, ref lprc, dwDTFormat, ref lpDTParams);
        }

        [DllImport("user32.dll")]
        static extern int DrawTextEx(IntPtr hdc, string lpchText, int cchText,
                                     ref RECT lprc, uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams);
        #endregion
    }

    public class CharRange
    {
        public ushort Pos { get; set; }

        public char Char { get; set; }
    }
}
